package com.xqboss.apps.service.nft;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xqboss.apps.constant.Config;
import com.xqboss.apps.constant.LockKey;
import com.xqboss.apps.domain.nft.Nft;
import com.xqboss.apps.domain.nft.NftPublishRecord;
import com.xqboss.apps.dto.nft.NftDto;
import com.xqboss.apps.dto.nft.NftSpoDto;
import com.xqboss.apps.enums.nft.NftTypeEnum;
import com.xqboss.apps.enums.nft.ResellEnum;
import com.xqboss.apps.mapper.nft.NftMapper;
import com.xqboss.apps.mapper.nft.NftPublishRecordMapper;
import com.xqboss.apps.service.equipment.EquipmentSmeltTemplateService;
import com.xqboss.apps.util.mh.CommonUtil;
import com.xqboss.apps.vo.nft.NftVo;
import com.xqboss.chain.service.EChainService;
import com.xqboss.common.core.redis.lock.LockValue;
import com.xqboss.common.core.redis.lock.RedisLock;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.common.utils.LocalDateUtils;
import com.xqboss.common.utils.SecurityUtils;
import com.xqboss.common.utils.spring.SpringUtils;
import com.xqboss.system.service.ISysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * 藏品、装备Service接口
 *
 * @author xqboss
 * @date 2023-08-22
 */
@Slf4j
@Service
public class NftService extends ServiceImpl<NftMapper, Nft> {

    @Resource
    private NftMapper nftMapper;
    @Resource
    private NftPublishRecordMapper nftPublishRecordMapper;
    @Lazy
    @Autowired
    private EChainService eChainService;
    @Lazy
    @Autowired
    private NftNumberService nftNumberService;

    @Lazy
    @Autowired
    private EquipmentSmeltTemplateService equipmentSmeltTemplateService;

    @Transactional(rollbackFor = Exception.class)
    public void update(NftDto dto) {
        if (dto.getIsResell() == ResellEnum.OPEN) {
            if (Objects.isNull(dto.getLowestPrice()) || Objects.isNull(dto.getMaxPrice())) {
                throw new ServiceException("价格区间不能为空");
            }
        }
        if (Objects.nonNull(dto.getLowestPrice()) && Objects.nonNull(dto.getMaxPrice())) {
            if (dto.getLowestPrice().compareTo(dto.getMaxPrice()) > 0) {
                throw new ServiceException("最低价小于或等于最高价");
            }
        }
        Nft nft = BeanUtil.copyProperties(dto, Nft.class, "totalStock", "stock", "createTime");
        nft.setUpdateTime(LocalDateUtils.getCurrentLocalDateTime());
        boolean b = baseMapper.updateNftById(nft);
        //插入市场表数据(换单独接口处理上架市场)
//        marketMainService.addMarket(nft);
        if (!b) {
            throw new ServiceException("更新失败！");
        }
        // 如果是装备。如果有熔炼模版id,则保存到熔炼模板id
        if (nft.getNftType() == NftTypeEnum.EQUIPMENT) {
            if (ObjUtil.isNotEmpty(dto.getSmeltTemplateId())) {
                equipmentSmeltTemplateService.nftSave(nft.getId(), dto.getSmeltTemplateId());
            }
        }
    }

    public List<NftVo> selectListByDto(NftDto dto) {

        return nftMapper.selectNftList(dto);
    }

    @Transactional(rollbackFor = Exception.class)
    public void insert(NftDto dto) {
        if (ObjUtil.isEmpty(dto.getStock()) || dto.getStock() <= 0) {
            throw new ServiceException("库存数错误");
        }
        if (dto.getIsResell() == ResellEnum.OPEN) {
            if (Objects.isNull(dto.getLowestPrice()) || Objects.isNull(dto.getMaxPrice())) {
                throw new ServiceException("价格区间不能为空");
            }
        }
        if (Objects.nonNull(dto.getLowestPrice()) && Objects.nonNull(dto.getMaxPrice())) {
            if (dto.getLowestPrice().compareTo(dto.getMaxPrice()) > 0) {
                throw new ServiceException("最低价小于或等于最高价");
            }
        }
        if (dto.getNftType() == null) {
            throw new ServiceException("藏品类型不能为空");
        }
        if (dto.getNftType().getValue() == 2) {
            Objects.requireNonNull(dto.getAttrTemplateId(), "属性模板Id不能为空");
        }
        Nft nft = BeanUtil.copyProperties(dto, Nft.class);
        // 设置藏品编号前缀
        String prefix = CommonUtil.getNftPrefix();
        nft.setNftExtra(prefix);
        nft.setTotalStock(nft.getStock());
//        nft.setDisplayStock(nft.getStock());
//        nft.setCirculatingSupply(nft.getStock());
        nft.setStatus(2);
        nft.setContract(eChainService.createContract());
        //藏品装备没有开始发售时间，默认当前时间
        if (ObjUtil.isNull(nft.getPublishTime())) {
            nft.setPublishTime(LocalDateUtils.getCurrentLocalDateTime());
        }
        //保存藏品
        boolean b = this.save(nft);
        if (!b) {
            throw new ServiceException("新增典藏失败");
        }

        // 如果是装备。如果有熔炼模版id,则保存到熔炼模板id
        if (nft.getNftType() == NftTypeEnum.EQUIPMENT) {
            if (ObjUtil.isNotEmpty(dto.getSmeltTemplateId())) {
                equipmentSmeltTemplateService.nftSave(nft.getId(), dto.getSmeltTemplateId());
            }
        }

        generateNum(nft.getId(), nft.getStock(), 0L, true, prefix);
        NftPublishRecord record = new NftPublishRecord();
        record.setUserId(SecurityUtils.getUserId());
        record.setNftId(nft.getId());
        record.setOperName(SecurityUtils.getUsername());
        record.setAddCount(dto.getStock());
        record.setType(1);
        nftPublishRecordMapper.insert(record);
    }

    /**
     * 锁定库存
     */
    @RedisLock(lockKey = LockKey.LOCK_NFT_STOCK)
    @Transactional(rollbackFor = Exception.class)
    public void lockStock(@LockValue Long nftId, int count) {
        if (count <= 0) {
            throw new ServiceException("锁定数量错误！");
        }
        Nft nft = getById(nftId);
        if (Objects.isNull(nft)) {
            throw new ServiceException("典藏不存在！");
        }
        if (nft.getStock() < count) {
            throw new ServiceException("库存不足！");
        }
        boolean update = update(new LambdaUpdateWrapper<Nft>()
                .setSql("stock=stock-" + count)
                .setSql("lock_stock=lock_stock+" + count)
                .eq(Nft::getId, nftId)
                .ge(Nft::getStock, count));
        if (!update) {
            throw new ServiceException("操作失败！");
        }
    }

    @RedisLock(lockKey = LockKey.LOCK_NFT_STOCK)
    @Transactional(rollbackFor = Exception.class)
    public void unlockStock(@LockValue Long nftId, int count) {
        if (count <= 0) {
            throw new ServiceException("锁定数量错误！");
        }
        Nft nft = getById(nftId);
        if (Objects.isNull(nft)) {
            throw new ServiceException("典藏不存在！");
        }
        if (nft.getLockStock() < count) {
            throw new ServiceException("库存不足！");
        }
        boolean update = update(new LambdaUpdateWrapper<Nft>()
                .setSql("stock=stock+" + count)
                .setSql("lock_stock=lock_stock-" + count)
                .eq(Nft::getId, nftId)
                .ge(Nft::getLockStock, count));
        if (!update) {
            throw new ServiceException("操作失败！");
        }
    }

    /**
     * 扣除锁定库存
     */
    @RedisLock(lockKey = LockKey.LOCK_NFT_STOCK)
    @Transactional(rollbackFor = Exception.class)
    public void takeStock(@LockValue Long nftId, int count) {
        if (count <= 0) {
            throw new ServiceException("锁定数量错误！");
        }
        boolean update = update(new LambdaUpdateWrapper<Nft>()
                .setSql("lock_stock=lock_stock-" + count)
                .eq(Nft::getId, nftId)
                .ge(Nft::getLockStock, count));
        if (!update) {
            throw new ServiceException("库存不足！");
        }
    }

    public Nft requireNft(Long nftId) {
        Nft nft = getNft(nftId);
        if(Objects.isNull(nft)){
            throw new ServiceException("典藏不存在！");
        }
        return nft;
    }

    public Nft getNft(Long nftId){
        return getById(nftId);
    }

    /**
     * 获取NFT转卖状态
     * @param nftId
     * @return
     */
    public boolean isBoxResell(Long nftId) {
        Nft nft = getOne(new LambdaQueryWrapper<Nft>()
                .select(Nft::getIsResell)
                .eq(Nft::getId, nftId));
        if(Objects.nonNull(nft)){
            return nft.getIsResell() == ResellEnum.OPEN;
        }
        return false;
    }

    /**
     *
     * 通过藏品id获取主图（身份卡藏品id 或 碎片藏品id）
     *
     * @param identityCardNftId  藏品id
     * @return url
     */
    public String getNftMainImg(Long identityCardNftId) {
        Nft nft = this.getOne(new LambdaQueryWrapper<Nft>().select(Nft::getMainImg).eq(Nft::getId, identityCardNftId));
        Assert.notNull(nft,"当前nft不存在！");
        return nft.getMainImg();
    }

    /**
     * 增发藏品
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    public void nftSpo(NftSpoDto dto) {
        // 获取藏品信息
        Nft nft = getNft(dto.getNftId());
        // 获取该藏品最大编号
        long beginNumberValue = nftNumberService.getNftNumberMaxValue(nft.getId());
        generateNum(nft.getId(), dto.getCount(), beginNumberValue, true, nft.getNftExtra());
        NftPublishRecord record = new NftPublishRecord();
        record.setUserId(SecurityUtils.getUserId());
        record.setNftId(nft.getId());
        record.setOperName(SecurityUtils.getUsername());
        record.setAddCount(dto.getCount());
        record.setType(2);
        nftPublishRecordMapper.insert(record);
        update(Wrappers.<Nft>lambdaUpdate()
                .setSql("stock = stock + " + dto.getCount())
                .setSql("total_stock = total_stock + " + dto.getCount())
                .setSql("display_stock = display_stock + " + dto.getCount())
                .setSql("circulating_supply = circulating_supply + " + dto.getCount())
                .eq(Nft::getId, dto.getNftId()));
    }


    /**
     * 提前生成编号
     *
     * @param nftId
     */
    public void generateNum(Long nftId, Integer stock, Long beginNumValue, Boolean numRandom, String prefix) {
        if (stock <= 0) {
            return;
        }
        List<String> arr = new ArrayList<>();
        for (int i = 1; i <= stock; i++) {
            arr.add(i + beginNumValue + "");
        }
        if (BooleanUtils.isTrue(numRandom)) {
            Collections.shuffle(arr);
        }
        // 写入编号到数据库
        nftNumberService.createNftNumber(nftId, arr, prefix);
    }

    /**
     * 获取藏品发放记录
     * @return 发布记录
     */
    public List<NftPublishRecord> getPublishList(Long nftId, Integer type) {
        LambdaQueryWrapper<NftPublishRecord> wrapper = Wrappers.<NftPublishRecord>lambdaQuery()
                .eq(NftPublishRecord::getNftId, nftId)
                .orderByDesc(NftPublishRecord::getId);
        if(Objects.nonNull(type)){
            wrapper.eq(NftPublishRecord::getType, type);
        }
        return nftPublishRecordMapper.selectList(wrapper);
    }

    /**
     * 获取打卡空投热豆
     * 1、打卡空投热豆 ＝ 幸运值 × 收益折算系数
     * 2、若装备详情配置了收益折算系数，对应装备以详情配置为准
     */
    public BigDecimal getPunchAirdropBeans(Long nftId, Integer luck) {
        Nft nft = this.getNft(nftId);
        log.info("获取打卡空投热豆计算nft：{}", nft);
        if (Objects.nonNull(nft) && nft.getNftType() == NftTypeEnum.EQUIPMENT) {
            // 如果nft没有配置收益折算系数或者为null，使用系统公共的收益折算系数，四舍五入保留两位小数 使用Optional.ofNullable()方法
            return Optional.ofNullable(nft.getCoefficient())
                    .orElseGet(() -> SpringUtils.getBean(ISysConfigService.class)
                            .getValue(Config.Sys.Punch.收益折算系数))
                    .multiply(luck == null ? BigDecimal.ZERO : BigDecimal.valueOf(luck))
                    .setScale(2, RoundingMode.HALF_UP);
        }
        return BigDecimal.ZERO;
    }
}
